#include "sdk.h"
#include <wx/toolbar.h>
#include "M_Code_debugger_interfaces.h"
#include "M_Code_StyledTextCtrl.h"
#include "ccmanager.h"
#include "debuggermanager.h"
#include "editor_hooks.h"
#include "loggers.h"
#include <errno.h>
#include <signal.h>
#include <sys/types.h>

M_Code_Plugin::M_Code_Plugin() :
  m_Type( ptNone ),
  m_IsAttached( false ) {
  SetEvtHandlerEnabled( false );
}

M_Code_Plugin::~M_Code_Plugin() {
}

void M_Code_Plugin::Attach() {
  if( m_IsAttached ) {
    return;
  }
  wxWindow* window = Manager::Get()->GetAppWindow();
  if( window ) {
    window->PushEventHandler( this );
  }
  m_IsAttached = true;
  OnAttach();
  SetEvtHandlerEnabled( true );
  CodeBlocksEvent event( cbEVT_PLUGIN_ATTACHED );
  event.SetPlugin( this );
  Manager::Get()->ProcessEvent( event );
}

void M_Code_Plugin::Release( bool appShutDown ) {
  if( !m_IsAttached ) {
    return;
  }
  m_IsAttached = false;
  SetEvtHandlerEnabled( false );
  OnRelease( appShutDown );
  CodeBlocksEvent event( cbEVT_PLUGIN_RELEASED );
  event.SetPlugin( this );
  Manager::Get()->ProcessEvent( event );
  if( appShutDown ) {
    return;
  }
  wxWindow* window = Manager::Get()->GetAppWindow();
  if( window ) {
    window->RemoveEventHandler( this );
  }
}

void M_Code_Plugin::NotImplemented( const wxString& log ) const {
  Manager::Get()->GetLogManager()->DebugLog( log + _T( " : 未实施" ) );
}

cbCompilerPlugin::cbCompilerPlugin() {
  m_Type = ptCompiler;
}

cbDebuggerPlugin::cbDebuggerPlugin( const wxString &guiName, const wxString &settingsName ) :
  m_pCompiler( nullptr ),
  m_WaitingCompilerToFinish( false ),
  m_StartType( StartTypeUnknown ),
  m_ActiveConfig( 0 ),
  m_LogPageIndex( -1 ),
  m_lastLineWasNormal( true ),
  m_guiName( guiName ),
  m_settingsName( settingsName ) {
  m_Type = ptDebugger;
}

void cbDebuggerPlugin::OnAttach() {
  Manager::Get()->GetDebuggerManager()->RegisterDebugger( this );
  OnAttachReal();
  typedef cbEventFunctor<cbDebuggerPlugin, CodeBlocksEvent> Event;
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_OPEN, new Event( this, &cbDebuggerPlugin::OnEditorOpened ) );
  Manager::Get()->RegisterEventSink( cbEVT_PROJECT_ACTIVATE, new Event( this, &cbDebuggerPlugin::OnProjectActivated ) );
  Manager::Get()->RegisterEventSink( cbEVT_PROJECT_CLOSE, new Event( this, &cbDebuggerPlugin::OnProjectClosed ) );
  Manager::Get()->RegisterEventSink( cbEVT_COMPILER_FINISHED, new Event( this, &cbDebuggerPlugin::OnCompilerFinished ) );
  m_StartType = StartTypeUnknown;
  if( SupportsFeature( cbDebuggerFeature::ValueTooltips ) ) {
    RegisterValueTooltip();
  }
}

void cbDebuggerPlugin::OnRelease( bool appShutDown ) {
  Manager::Get()->RemoveAllEventSinksFor( this );
  OnReleaseReal( appShutDown );
  Manager::Get()->GetDebuggerManager()->UnregisterDebugger( this );
}

void cbDebuggerPlugin::BuildMenu( M_Code_unused wxMenuBar* menuBar ) {
  if( !IsAttached() ) {
    return;
  }
}

wxString cbDebuggerPlugin::GetEditorWordAtCaret( const wxPoint* mousePosition ) {
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !ed ) {
    return wxEmptyString;
  }
  M_Code_StyledTextCtrl* stc = ed->GetControl();
  if( !stc ) {
    return wxEmptyString;
  }
  wxString selected_text = stc->GetSelectedText();
  if( selected_text != wxEmptyString ) {
    selected_text.Trim( true );
    selected_text.Trim( false );
    wxString::size_type pos = selected_text.find( _T( '\n' ) );
    if( pos != wxString::npos ) {
      selected_text.Remove( pos, selected_text.length() - pos );
      selected_text.Trim( true );
      selected_text.Trim( false );
    }
    if( mousePosition ) {
      int startPos = stc->GetSelectionStart();
      int endPos = stc->GetSelectionEnd();
      int mousePos = stc->PositionFromPointClose( mousePosition->x, mousePosition->y );
      if( mousePos == wxSCI_INVALID_POSITION ) {
        return wxEmptyString;
      } else if( startPos <= mousePos && mousePos <= endPos ) {
        return selected_text;
      } else
      { return wxEmptyString; }
    } else
    { return selected_text; }
  }
  if( mousePosition ) {
    int pos = stc->PositionFromPoint( *mousePosition );
    int start = stc->WordStartPosition( pos, true );
    int end = stc->WordEndPosition( pos, true );
    selected_text = stc->GetTextRange( start, end );
  } else {
    int start = stc->WordStartPosition( stc->GetCurrentPos(), true );
    int end = stc->WordEndPosition( stc->GetCurrentPos(), true );
    selected_text = stc->GetTextRange( start, end );
  }
  return selected_text;
}

void cbDebuggerPlugin::BuildModuleMenu( const ModuleType type, wxMenu* menu, M_Code_unused const FileTreeData* data ) {
  if( !IsAttached() ) {
    return;
  }
  if( type != mtEditorManager || !menu ) {
    return;
  }
  cbDebuggerPlugin *active_plugin = Manager::Get()->GetDebuggerManager()->GetActiveDebugger();
  if( active_plugin != this ) {
    return;
  }
  wxString word;
  if( IsRunning() ) {
    word = GetEditorWordAtCaret();
  }
  Manager::Get()->GetDebuggerManager()->BuildContextMenu( *menu, word, IsRunning() );
}

bool cbDebuggerPlugin::BuildToolBar( M_Code_unused wxToolBar* toolBar ) {
  return false;
}

bool cbDebuggerPlugin::ToolMenuEnabled() const {
  M_Code_Project* prj = Manager::Get()->GetProjectManager()->GetActiveProject();
  bool en = ( prj && !prj->GetCurrentlyCompilingTarget() ) || IsAttachedToProcess();
  return IsRunning() && en;
}

cbDebuggerConfiguration& cbDebuggerPlugin::GetActiveConfig() {
  DebuggerManager::RegisteredPlugins &allPlugins = Manager::Get()->GetDebuggerManager()->GetAllDebuggers();
  DebuggerManager::RegisteredPlugins::iterator it = allPlugins.find( this );
  if( it == allPlugins.end() ) {
    cbAssert( false );
  }
  cbDebuggerConfiguration *config = it->second.GetConfiguration( m_ActiveConfig );
  if( !config ) {
    return *it->second.GetConfigurations().front();
  } else {
    return *config;
  }
}

void cbDebuggerPlugin::SetActiveConfig( int index ) {
  m_ActiveConfig = index;
}

int cbDebuggerPlugin::GetIndexOfActiveConfig() const {
  return m_ActiveConfig;
}

void cbDebuggerPlugin::ClearActiveMarkFromAllEditors() {
  EditorManager* edMan = Manager::Get()->GetEditorManager();
  for( int i = 0; i < edMan->GetEditorsCount(); ++i ) {
    M_Code_Editor* ed = edMan->GetBuiltinEditor( i );
    if( ed ) {
      ed->SetDebugLine( -1 );
    }
  }
}

cbDebuggerPlugin::SyncEditorResult cbDebuggerPlugin::SyncEditor( const wxString& filename, int line, bool setMarker ) {
  if( setMarker ) {
    EditorManager* edMan = Manager::Get()->GetEditorManager();
    for( int i = 0; i < edMan->GetEditorsCount(); ++i ) {
      M_Code_Editor* ed = edMan->GetBuiltinEditor( i );
      if( ed ) {
        ed->SetDebugLine( -1 );
      }
    }
  }
  FileType ft = FileTypeOf( filename );
  if( ft != ftSource && ft != ftHeader && ft != ftResource && ft != ftTemplateSource ) {
    if( line < 0 || ft != ftOther ) {
      ShowLog( false );
      Log( _T( "Unknown file: " ) + filename, Logger::error );
      InfoWindow::Tip( _T( "File: " ) + filename, _T( "Unknown file" ), 5000 );
      return SyncFileUnknown;
    }
  }
  M_Code_Project* project = Manager::Get()->GetProjectManager()->GetActiveProject();
  ProjectFile* f = project ? project->GetFileByFilename( filename, false, true ) : nullptr;
  wxString unixfilename = UnixFilename( filename );
  wxFileName fname( unixfilename );
  if( project && fname.IsRelative() ) {
    fname.MakeAbsolute( project->GetBasePath() );
  }
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->Open( fname.GetLongPath() );
  if( ed ) {
    ed->Show( true );
    if( f && !ed->GetProjectFile() ) {
      ed->SetProjectFile( f );
    }
    ed->GotoLine( line - 1, false );
    if( setMarker ) {
      ed->SetDebugLine( line - 1 );
    }
    return SyncOk;
  } else {
    ShowLog( false );
    Log( _T( "Cannot open file: " ) + filename, Logger::error );
    InfoWindow::Tip( _T( "File: " ) + filename, _T( "Cannot open file" ), 5000 );
    return SyncFileNotFound;
  }
}

inline bool HasBreakpoint( cbDebuggerPlugin &plugin, wxString const &filename, int line ) {
  int count = plugin.GetBreakpointsCount();
  for( int ii = 0; ii < count; ++ii ) {
    const cb::shared_ptr<cbBreakpoint> &b = plugin.GetBreakpoint( ii );
    if( b->GetLocation() == filename && b->GetLine() == line ) {
      return true;
    }
  }
  return false;
}

void cbDebuggerPlugin::EditorLinesAddedOrRemoved( M_Code_Editor* editor, int startline, int lines ) {
  if( !editor || lines == 0 ) {
    return;
  }
  const wxString& filename = editor->GetFilename();
  std::vector<int> breakpoints_for_file;
  int count = GetBreakpointsCount();
  for( int ii = 0; ii < count; ++ii ) {
    const cb::shared_ptr<cbBreakpoint> &b = GetBreakpoint( ii );
    if( b->GetLocation() == filename ) {
      breakpoints_for_file.push_back( ii );
    }
  }
  if( lines < 0 ) {
    lines = -lines;
    int endline = startline + lines - 1;
    std::vector<cb::shared_ptr<cbBreakpoint>> to_remove;
    for( std::vector<int>::iterator it = breakpoints_for_file.begin(); it != breakpoints_for_file.end(); ++it ) {
      const cb::shared_ptr<cbBreakpoint> &b = GetBreakpoint( *it );
      if( b->GetLine() > endline ) {
        ShiftBreakpoint( *it, -lines );
      } else if( b->GetLine() >= startline && b->GetLine() <= endline ) {
        to_remove.push_back( b );
      }
    }
    for( std::vector<cb::shared_ptr<cbBreakpoint>>::iterator it = to_remove.begin(); it != to_remove.end(); ++it ) {
      DeleteBreakpoint( *it );
    }
  } else {
    for( std::vector<int>::iterator it = breakpoints_for_file.begin(); it != breakpoints_for_file.end(); ++it ) {
      const cb::shared_ptr<cbBreakpoint> &b = GetBreakpoint( *it );
      if( b->GetLine() > startline ) {
        ShiftBreakpoint( *it, lines );
      }
    }
  }
}

void cbDebuggerPlugin::OnEditorOpened( CodeBlocksEvent& event ) {
  EditorBase* ed = event.GetEditor();
  if( ed && ed->IsBuiltinEditor() ) {
    M_Code_Editor *editor = static_cast<M_Code_Editor*>( ed );
    editor->RefreshBreakpointMarkers();
    if( IsRunning() ) {
      wxString filename;
      int line;
      GetCurrentPosition( filename, line );
      wxFileName edFileName( ed->GetFilename() );
      edFileName.Normalize();
      wxFileName dbgFileName( filename );
      dbgFileName.Normalize();
      if( dbgFileName.GetFullPath().IsSameAs( edFileName.GetFullPath() ) && line != -1 ) {
        editor->SetDebugLine( line - 1 );
      }
    }
  }
  event.Skip();
}

void cbDebuggerPlugin::OnProjectActivated( CodeBlocksEvent& event ) {
  event.Skip();
  if( this != Manager::Get()->GetDebuggerManager()->GetActiveDebugger() ) {
    return;
  }
  if( !IsRunning() ) {
    return;
  }
  if( event.GetProject() != GetProject() && GetProject() ) {
    wxString msg = _T( "在积极调试另一个项目时，不能更改活动项目.\n"
                       "Do you want to stop debugging?\n\n"
                       "Click \"Yes\" to stop debugging now or click \"No\" to re-activate the debuggee." );
    if( msging( msg, _T( "警告" ), wxICON_WARNING | wxYES_NO ) == wxID_YES ) {
      Stop();
    } else
    { Manager::Get()->GetProjectManager()->SetProject( GetProject() ); }
  }
}

void cbDebuggerPlugin::OnProjectClosed( CodeBlocksEvent& event ) {
  event.Skip();
  if( this != Manager::Get()->GetDebuggerManager()->GetActiveDebugger() ) {
    return;
  }
  CleanupWhenProjectClosed( event.GetProject() );
  if( !IsRunning() ) {
    return;
  }
  if( event.GetProject() == GetProject() ) {
    InfoWindow::Tip( _T( "正在调试的项目已关闭 调试会话将立即终止" ), _T( "提示" ), 2500 );
    Stop();
    ResetProject();
  }
}



void cbDebuggerPlugin::ShowLog( bool clear ) {
  TextCtrlLogger *log = Manager::Get()->GetDebuggerManager()->GetLogger();
  if( log ) {
    CodeBlocksLogEvent eventSwitchLog( cbEVT_SWITCH_TO_LOG_WINDOW, log );
    Manager::Get()->ProcessEvent( eventSwitchLog );
    CodeBlocksLogEvent eventShowLog( cbEVT_SHOW_LOG_MANAGER );
    Manager::Get()->ProcessEvent( eventShowLog );
    if( clear ) {
      log->Clear();
    }
  }
}

void cbDebuggerPlugin::Log( const wxString& msg, Logger::level level ) {
  if( IsAttached() ) {
    Manager::Get()->GetLogManager()->Log( ( m_lastLineWasNormal ? wxEmptyString : _T( "\n" ) ) + msg, m_LogPageIndex, level );
    m_lastLineWasNormal = true;
  }
}

void cbDebuggerPlugin::DebugLog( const wxString& msg, Logger::level level ) {
  if( IsAttached() && HasDebugLog() ) {
    Manager::Get()->GetLogManager()->Log( ( !m_lastLineWasNormal ? _T( "[debug]" ) : _T( "\n[debug]" ) ) + msg,
                                          m_LogPageIndex, level );
    m_lastLineWasNormal = false;
  }
}

bool cbDebuggerPlugin::HasDebugLog() const {
  return M_Code_DebuggerCommonConfig::GetFlag( M_Code_DebuggerCommonConfig::ShowDebuggersLog );
}

void cbDebuggerPlugin::ClearLog() {
  Manager::Get()->GetDebuggerManager()->GetLogger()->Clear();
}

void cbDebuggerPlugin::SetupLog( int normalIndex ) {
  m_LogPageIndex = normalIndex;
}

void cbDebuggerPlugin::SwitchToDebuggingLayout() {
  CodeBlocksLayoutEvent queryEvent( cbEVT_QUERY_VIEW_LAYOUT );
  const cbDebuggerConfiguration &config = GetActiveConfig();
  wxString perspectiveName;
  switch( M_Code_DebuggerCommonConfig::GetPerspective() ) {
    case M_Code_DebuggerCommonConfig::OnePerDebugger:
      perspectiveName = GetGUIName();
      break;
    case M_Code_DebuggerCommonConfig::OnePerDebuggerConfig:
      perspectiveName = GetGUIName() + _T( ":" ) + config.GetName();
      break;
    case M_Code_DebuggerCommonConfig::OnlyOne:
    default:
      perspectiveName = _T( "Debugging" );
  }
  CodeBlocksLayoutEvent switchEvent( cbEVT_SWITCH_VIEW_LAYOUT, perspectiveName );
  Manager::Get()->GetLogManager()->Log( F( _T( "将布局切换到 \"%s\"" ), switchEvent.layout.wx_str() ) );
  Manager::Get()->ProcessEvent( queryEvent );
  m_PreviousLayout = queryEvent.layout;
  Manager::Get()->ProcessEvent( switchEvent );
  ShowLog( false );
}

void cbDebuggerPlugin::SwitchToPreviousLayout() {
  CodeBlocksLayoutEvent switchEvent( cbEVT_SWITCH_VIEW_LAYOUT, m_PreviousLayout );
  wxString const &name = !switchEvent.layout.IsEmpty() ? switchEvent.layout : wxString( _T( "default" ) );
  Manager::Get()->GetLogManager()->Log( F( _T( "将布局切换到 \"%s\"" ), name.wx_str() ) );
  Manager::Get()->ProcessEvent( switchEvent );
}

bool cbDebuggerPlugin::GetDebuggee( wxString &pathToDebuggee, wxString &workingDirectory, ProjectBuildTarget* target ) {
  if( !target ) {
    return false;
  }
  wxString out;
  switch( target->GetTargetType() ) {
    case ttExecutable:
    case ttConsoleOnly:
    case ttNative: {
      out = UnixFilename( target->GetOutputFilename() );
      Manager::Get()->GetMacrosManager()->ReplaceEnvVars( out );
      wxFileName f( out );
      f.MakeAbsolute( target->GetParentProject()->GetBasePath() );
      out = f.GetFullPath();
      Log( _T( "Adding file: " ) + out );
      ConvertDirectory( out );
    }
    break;
    case ttStaticLib:
    case ttDynamicLib:
      if( target->GetHostApplication().IsEmpty() ) {
        msging( _T( "必须选择要“运行”库的主机应用程序..." ) );
        return false;
      }
      out = UnixFilename( target->GetHostApplication() );
      Manager::Get()->GetMacrosManager()->ReplaceEnvVars( out );
      Log( _T( "Adding file: " ) + out );
      ConvertDirectory( out );
      break;
    case ttCommandsOnly:
    default:
      Log( _T( "不支持的目标类型（项目->属性->生成目标->类型）" ), Logger::error );
      return false;
  }
  if( out.empty() ) {
    Log( _T( "找不到调试对象的路径！" ), Logger::error );
    return false;
  }
  workingDirectory = target->GetWorkingDir();
  Manager::Get()->GetMacrosManager()->ReplaceEnvVars( workingDirectory );
  wxFileName cd( workingDirectory );
  if( cd.IsRelative() ) {
    cd.MakeAbsolute( target->GetParentProject()->GetBasePath() );
  }
  workingDirectory = cd.GetFullPath();
  pathToDebuggee = out;
  return true;
}

bool cbDebuggerPlugin::EnsureBuildUpToDate( StartType startType ) {
  m_StartType = startType;
  m_WaitingCompilerToFinish = false;
  if( !IsAttachedToProcess() ) {
    if( !M_Code_DebuggerCommonConfig::GetFlag( M_Code_DebuggerCommonConfig::AutoBuild ) ) {
      m_WaitingCompilerToFinish = false;
      m_pCompiler = nullptr;
      return true;
    }
    const std::vector<cbCompilerPlugin*> &compilers = Manager::Get()->GetPluginManager()->GetCompilerPlugins();
    if( compilers.empty() ) {
      m_pCompiler = nullptr;
    } else
    { m_pCompiler = compilers.front(); }
    if( m_pCompiler ) {
      if( m_pCompiler->IsRunning() ) {
        Log( _T( "使用中的编译器..." ) );
        Log( _T( "正在中止调试会话" ) );
        msging( _T( "编译器当前正在使用中。正在中止调试会话..." ),
                _T( "编译器正在运行" ), wxICON_WARNING );
        return false;
      }
      Log( _T( "确保来源最新的建筑" ) );
      m_WaitingCompilerToFinish = true;
      m_pCompiler->Build();
    }
  }
  return true;
}

void cbDebuggerPlugin::OnCompilerFinished( M_Code_unused CodeBlocksEvent& event ) {
  if( m_WaitingCompilerToFinish ) {
    m_WaitingCompilerToFinish = false;
    bool compilerFailed = false;
    if( m_pCompiler && m_pCompiler->GetExitCode() != 0 ) {
      InfoWindow::Tip( _T( "生成失败调试无法进行" ), _T( "调试中断" ), 2500 );
      //      AnnoyingDialog dlg(_T("调试中断?"), _T("生成失败，是否要调试程序?"),
      //                         wxART_QUESTION, AnnoyingDialog::YES_NO, AnnoyingDialog::rtNO);
      //      if(dlg.ShowModal() != AnnoyingDialog::rtYES) {
      //        ProjectManager *manager = Manager::Get()->GetProjectManager();
      //        if(manager->GetIsRunning() && manager->GetIsRunning() == this)
      //          manager->SetIsRunning(nullptr);
      //        compilerFailed = true;
      //      }
    }
    ShowLog( false );
    if( !CompilerFinished( compilerFailed, m_StartType ) ) {
      ProjectManager *manager = Manager::Get()->GetProjectManager();
      if( manager->GetIsRunning() && manager->GetIsRunning() == this ) {
        manager->SetIsRunning( nullptr );
      }
    }
  }
}

namespace {
  wxString MakeSleepCommand() {
    return F( _T( "sleep %lu" ), 80000000 + ::wxGetProcessId() );
  }

  struct ConsoleInfo {
    ConsoleInfo( const wxString &path = wxEmptyString, int pid = -1 ) : ttyPath( path ), sleepPID( pid ) {}

    bool IsValid() const { return !ttyPath.empty() && sleepPID > 0; }

    wxString ttyPath;
    int sleepPID;
  };

  ConsoleInfo GetConsoleTty( int consolePID ) {
    wxArrayString psOutput;
    wxArrayString psErrors;
    int result = wxExecute( _T( "ps x -o tty,pid,command" ), psOutput, psErrors, wxEXEC_SYNC );
    if( result != 0 ) {
      return ConsoleInfo();
    }
    const wxString &uniqueSleepTimeStr = MakeSleepCommand();
    int count = psOutput.GetCount();
    for( int i = count - 1; i > -1; --i ) {
      const wxString &psCmd = psOutput.Item( i );
      if( psCmd.Contains( uniqueSleepTimeStr ) ) {
        long pidForLine;
        if( psCmd.AfterFirst( ' ' ).Trim( false ).BeforeFirst( ' ' ).Trim( true ).ToLong( &pidForLine ) ) {
          if( pidForLine != consolePID ) {
            return ConsoleInfo( _T( "/dev/" ) + psCmd.BeforeFirst( ' ' ), pidForLine );
          }
        }
      }
    }
    return ConsoleInfo();
  }

  struct ConsoleProcessTerminationInfo {
    ConsoleProcessTerminationInfo() : status( -1 ), terminated( false ) {}

    bool FailedToStart() const { return terminated && status != 0; }

    int status;
    bool terminated;
  };

  struct ConsoleProcess : wxProcess {
    ConsoleProcess( cb::shared_ptr<ConsoleProcessTerminationInfo> cptInfo ) : info( cptInfo ) {}
    virtual void OnTerminate( int pid, int status ) {
      info->terminated = true;
      info->status = status;
    }
    cb::shared_ptr<ConsoleProcessTerminationInfo> info;
  };

}

int cbDebuggerPlugin::RunNixConsole( wxString &consoleTty ) {
  consoleTty = wxEmptyString;
  wxString cmd;
  int consolePid = 0;
  wxString term = Manager::Get()->GetConfigManager( _T( "app" ) )->Read( _T( "/console_terminal" ), DEFAULT_CONSOLE_TERM );
  term.Replace( _T( "$TITLE" ), wxString( _T( "'" ) ) + _T( "Program Console" ) + _T( "'" ) );
  cmd << term << _T( " " );
  const wxString &sleepCommand = MakeSleepCommand();
  cmd << sleepCommand;
  Manager::Get()->GetMacrosManager()->ReplaceEnvVars( cmd );
  cb::shared_ptr<ConsoleProcessTerminationInfo> processInfo( new ConsoleProcessTerminationInfo );
  ConsoleProcess *process = new ConsoleProcess( processInfo );
  consolePid = wxExecute( cmd, wxEXEC_ASYNC, process );
  if( consolePid <= 0 ) {
    return -1;
  }
  for( int ii = 0; ii < 100; ++ii ) {
    Manager::Yield();
    ::wxMilliSleep( 200 );
    if( processInfo->FailedToStart() ) {
      Log( F( _T( "执行终端命令失败: '%s' (exit code: %d)" ),
              cmd.wx_str(), processInfo->status ), Logger::error );
      break;
    }
    const ConsoleInfo &info = GetConsoleTty( consolePid );
    if( !info.IsValid() ) {
      continue;
    }
    if( kill( consolePid, 0 ) == -1 && errno == ESRCH ) {
      DebugLog( F( _T( "控制台使用sleep命令的PID为 %d, TTY %s" ),
                   info.sleepPID, info.ttyPath.wx_str() ) );
      consoleTty = info.ttyPath;
      return info.sleepPID;
    } else {
      DebugLog( F( _T( "控制台使用终端的PID为 %d, TTY %s" ), info.sleepPID, info.ttyPath.wx_str() ) );
      consoleTty = info.ttyPath;
      return consolePid;
    }
  }
  if( consolePid != 0 ) {
    ::wxKill( consolePid );
  }
  return -1;
}

void cbDebuggerPlugin::MarkAsStopped() {
  Manager::Get()->GetProjectManager()->SetIsRunning( nullptr );
}

void cbDebuggerPlugin::BringCBToFront() {
  wxWindow* app = Manager::Get()->GetAppWindow();
  if( app ) {
    app->Raise();
  }
}

void cbDebuggerPlugin::RegisterValueTooltip() {
  typedef cbEventFunctor<cbDebuggerPlugin, CodeBlocksEvent> Event;
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_TOOLTIP, new Event( this, &cbDebuggerPlugin::ProcessValueTooltip ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_TOOLTIP_CANCEL,
                                     new Event( this, &cbDebuggerPlugin::CancelValueTooltip ) );
}

bool cbDebuggerPlugin::ShowValueTooltip( M_Code_unused int style ) {
  return false;
}

void cbDebuggerPlugin::OnValueTooltip( M_Code_unused const wxString& token, M_Code_unused const wxRect& evalRect ) {
}

void cbDebuggerPlugin::ProcessValueTooltip( CodeBlocksEvent& event ) {
  event.Skip();
  if( M_Code_DebuggerCommonConfig::GetFlag( M_Code_DebuggerCommonConfig::RequireCtrlForTooltips ) ) {
    if( !wxGetKeyState( WXK_CONTROL ) ) {
      return;
    }
  }
  if( Manager::Get()->GetDebuggerManager()->GetInterfaceFactory()->IsValueTooltipShown() ) {
    return;
  }
  if( !ShowValueTooltip( event.GetInt() ) ) {
    return;
  }
  EditorBase* base = event.GetEditor();
  M_Code_Editor* ed = base && base->IsBuiltinEditor() ? static_cast<M_Code_Editor*>( base ) : nullptr;
  if( !ed ) {
    return;
  }
  if( ed->IsContextMenuOpened() ) {
    return;
  }
  if( ed->GetControl()->CallTipActive() ) {
    ed->GetControl()->CallTipCancel();
  }
  wxPoint pt;
  pt.x = event.GetX();
  pt.y = event.GetY();
  const wxString &token = GetEditorWordAtCaret( &pt );
  if( !token.empty() ) {
    pt = ed->GetControl()->ClientToScreen( pt );
    OnValueTooltip( token, wxRect( pt.x - 5, pt.y, 10, 10 ) );
  }
}

void cbDebuggerPlugin::CancelValueTooltip( M_Code_unused CodeBlocksEvent& event ) {
  Manager::Get()->GetDebuggerManager()->GetInterfaceFactory()->HideValueTooltip();
}


cbToolPlugin::cbToolPlugin() {
  m_Type = ptTool;
}

cbMimePlugin::cbMimePlugin() {
  m_Type = ptMime;
}

cbCodeCompletionPlugin::cbCodeCompletionPlugin() {
  m_Type = ptCodeCompletion;
}

void cbCodeCompletionPlugin::DoAutocomplete( M_Code_unused const CCToken& token, M_Code_unused M_Code_Editor* ed ) {
}

void cbCodeCompletionPlugin::DoAutocomplete( const wxString& token, M_Code_Editor* ed ) {
  DoAutocomplete( CCToken( -1, token ), ed );
}

bool cbCodeCompletionPlugin::IsProviderFor( M_Code_Editor* ed ) {
  return ( Manager::Get()->GetCCManager()->GetProviderFor( ed ) == this );
}

cbWizardPlugin::cbWizardPlugin() {
  m_Type = ptWizard;
}

m_SmartIndentPlugin::m_SmartIndentPlugin() {
  m_Type = ptSmartIndent;
}

void m_SmartIndentPlugin::OnAttach() {
  m_FunctorId = EditorHooks::RegisterHook( new EditorHooks::cbSmartIndentEditorHookFunctor( this ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_CC_DONE, new cbEventFunctor<m_SmartIndentPlugin, CodeBlocksEvent>( this, &m_SmartIndentPlugin::OnCCDoneEvent ) );
}

void m_SmartIndentPlugin::OnRelease( M_Code_unused bool appShutDown ) {
  EditorHooks::UnregisterHook( m_FunctorId );
}

bool m_SmartIndentPlugin::AutoIndentEnabled()const {
  return Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/auto_indent" ), true );
}

bool m_SmartIndentPlugin::SmartIndentEnabled()const {
  return Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/smart_indent" ), true );
}

bool m_SmartIndentPlugin::BraceSmartIndentEnabled()const {
  return Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/brace_smart_indent" ), true );
}

bool m_SmartIndentPlugin::BraceCompletionEnabled()const {
  return Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/brace_completion" ), true );
}

bool m_SmartIndentPlugin::SelectionBraceCompletionEnabled()const {
  return Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/selection_brace_completion" ), false );
}

void m_SmartIndentPlugin::Indent( M_Code_StyledTextCtrl* stc, wxString &indent )const {
  if( stc->GetUseTabs() ) {
    indent << _T( '\t' );
  } else {
    indent << wxString( _T( ' ' ), stc->GetTabWidth() );
  }
}

bool m_SmartIndentPlugin::Indent( M_Code_StyledTextCtrl* stc, wxString &indent, int posInLine )const {
  if( posInLine >= 0 ) {
    if( stc->GetUseTabs() ) {
      indent = wxString( _T( '\t' ), posInLine / stc->GetTabWidth() );
    } else
    { indent = wxString( _T( ' ' ), posInLine ); }
    return true;
  }
  return false;
}

wxString m_SmartIndentPlugin::GetLastNonCommentWord( M_Code_Editor* ed, int position, unsigned int NumberOfWords )const {
  M_Code_StyledTextCtrl* stc = ed->GetControl();
  if( !stc ) {
    return wxEmptyString;
  }
  if( position == -1 ) {
    position = stc->GetCurrentPos();
  }
  wxString str;
  str.Empty();
  int count = 0;
  bool foundlf = false;
  while( position ) {
    wxChar c = stc->GetCharAt( --position );
    int style = stc->GetStyleAt( position );
    bool inComment = stc->IsComment( style );
    if( c == _T( '\n' ) ) {
      count++;
      foundlf = true;
    } else if( c == _T( '\r' ) && !foundlf ) {
      count++;
    } else
    { foundlf = false; }
    if( count > 1 ) {
      return str;
    }
    if( !inComment && c != _T( ' ' ) && c != _T( '\t' ) && c != _T( '\n' ) && c != _T( '\r' ) ) {
      int startpos = stc->WordStartPosition( position, true );
      for( unsigned int i = 1; i < NumberOfWords ; ++i ) {
        startpos = stc->WordStartPosition( startpos - 1, true );
      }
      int endpos = stc->WordEndPosition( startpos, true );
      str = stc->GetTextRange( startpos, endpos );
      return str;
    }
  }
  return str;
}

wxChar m_SmartIndentPlugin::GetLastNonWhitespaceChar( M_Code_Editor* ed, int position )const {
  return GetLastNonWhitespaceChars( ed, position, 1 )[0];
}

wxString m_SmartIndentPlugin::GetLastNonWhitespaceChars( M_Code_Editor* ed, int position, unsigned int NumberOfChars )const {
  M_Code_StyledTextCtrl* stc = ed->GetControl();
  if( !stc ) {
    return wxEmptyString;
  }
  if( position == -1 ) {
    position = stc->GetCurrentPos();
  }
  int count = 0;
  bool foundlf = false;
  while( position ) {
    wxChar c = stc->GetCharAt( --position );
    int style = stc->GetStyleAt( position );
    bool inComment = stc->IsComment( style );
    if( c == _T( '\n' ) ) {
      count++;
      foundlf = true;
    } else if( c == _T( '\r' ) && !foundlf ) {
      count++;
    } else {
      foundlf = false;
    }
    if( count > 1 ) {
      return wxEmptyString;
    }
    if( !inComment && c != _T( ' ' ) && c != _T( '\t' ) && c != _T( '\n' ) && c != _T( '\r' ) ) {
      return stc->GetTextRange( position - NumberOfChars + 1, position + 1 );
    }
  }
  return wxEmptyString;
}

wxChar m_SmartIndentPlugin::GetNextNonWhitespaceCharOnLine( M_Code_StyledTextCtrl* stc, int position, int *pos )const {
  if( position == -1 ) {
    position = stc->GetCurrentPos();
  }
  while( position < stc->GetLength() ) {
    wxChar c = stc->GetCharAt( position );
    if( c == _T( '\n' ) || c ==  _T( '\r' ) ) {
      if( pos ) {
        *pos = position;
      }
      return 0;
    }
    if( c !=  _T( ' ' ) && c != _T( '\t' ) ) {
      if( pos ) {
        *pos = position;
      }
      return c;
    }
    position++;
  }
  return 0;
}

int m_SmartIndentPlugin::FindBlockStart( M_Code_StyledTextCtrl* stc, int position, wxChar blockStart, wxChar blockEnd, M_Code_unused bool skipNested )const {
  int lvl = 0;
  wxChar b = stc->GetCharAt( position );
  while( b ) {
    if( b == blockEnd ) {
      ++lvl;
    } else if( b == blockStart ) {
      if( lvl == 0 ) {
        return position;
      }
      --lvl;
    }
    --position;
    b = stc->GetCharAt( position );
  }
  return -1;
}

int m_SmartIndentPlugin::FindBlockStart( M_Code_StyledTextCtrl* stc, int position, wxString blockStart, wxString blockEnd, bool CaseSensitive )const {
  int pos = position;
  int pb, pe;
  int lvl = 0;
  int flags = wxSCI_FIND_WHOLEWORD;
  if( CaseSensitive ) {
    flags |= wxSCI_FIND_MATCHCASE;
  }
  do {
    pb =  stc->FindText( pos, 0, blockStart, flags );
    pe =  stc->FindText( pos, 0, blockEnd, flags );
    if( pe > pb ) {
      pos = pe;
      ++lvl;
      continue;
    }
    pos = pb;
    if( lvl == 0 ) {
      return pb;
    }
    --lvl;
  } while( pos != -1 );
  return -1;
}

int m_SmartIndentPlugin::GetFirstBraceInLine( M_Code_StyledTextCtrl* stc, int string_style )const {
  int curr_position = stc->GetCurrentPos();
  int position = curr_position;
  int min_brace_position = position;
  int closing_braces = 0;
  bool found_brace = false;
  bool has_braces = false;
  while( position ) {
    wxChar c = stc->GetCharAt( --position );
    int style = stc->GetStyleAt( position );
    if( style == string_style ) {
      continue;
    }
    if( c == _T( ';' ) ) {
      found_brace = false;
      break;
    } else if( c == _T( ')' ) ) {
      ++closing_braces;
      has_braces = true;
    } else if( c == _T( '(' ) ) {
      has_braces = true;
      if( closing_braces > 0 ) {
        --closing_braces;
      } else if( !found_brace ) {
        min_brace_position = position + 1;
        found_brace = true;
        break;
      }
    } else if( c == _T( '\n' ) && position + 1 != curr_position && !has_braces ) {
      break;
    }
  }
  if( !found_brace ) {
    return -1;
  }
  int tab_characters = 0;
  while( position ) {
    wxChar c = stc->GetCharAt( --position );
    if( c == _T( '\n' ) && position + 1 != curr_position ) {
      break;
    } else if( c == _T( '\t' ) ) {
      ++tab_characters;
    }
  }
  if( stc->GetUseTabs() ) {
    position -= tab_characters * stc->GetTabWidth();
  }
  return min_brace_position - position - 1;
}

wxChar m_SmartIndentPlugin::GetNextNonWhitespaceCharOfLine( M_Code_StyledTextCtrl* stc, int position, int *pos )const {
  if( position == -1 ) {
    position = stc->GetCurrentPos();
  }
  while( position < stc->GetLength() ) {
    wxChar c = stc->GetCharAt( position );
    if( c == _T( '\n' ) || c ==  _T( '\r' ) ) {
      if( pos ) {
        *pos = position;
      }
      return 0;
    }
    if( c !=  _T( ' ' ) && c != _T( '\t' ) ) {
      if( pos ) {
        *pos = position;
      }
      return c;
    }
    position++;
  }
  return 0;
}

void m_SmartIndentPlugin::OnCCDoneEvent( CodeBlocksEvent& event ) {
  EditorBase* eb = event.GetEditor();
  if( eb && eb->IsBuiltinEditor() ) {
    M_Code_Editor* ed = static_cast<M_Code_Editor *>( eb );
    OnCCDone( ed );
  }
}
